/*
 * @(#)SignerInfo.java	1.48 06/10/10
 *
 * Copyright  1990-2008 Sun Microsystems, Inc. All Rights Reserved.  
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER  
 *   
 * This program is free software; you can redistribute it and/or  
 * modify it under the terms of the GNU General Public License version  
 * 2 only, as published by the Free Software Foundation.   
 *   
 * This program is distributed in the hope that it will be useful, but  
 * WITHOUT ANY WARRANTY; without even the implied warranty of  
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU  
 * General Public License version 2 for more details (a copy is  
 * included at /legal/license.txt).   
 *   
 * You should have received a copy of the GNU General Public License  
 * version 2 along with this work; if not, write to the Free Software  
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  
 * 02110-1301 USA   
 *   
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa  
 * Clara, CA 95054 or visit www.sun.com if you need additional  
 * information or have any questions. 
 *
 */

package sun.security.pkcs;

import java.io.OutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.security.cert.X509Certificate;
import java.security.*;
import java.util.ArrayList;

import sun.security.util.*;
import sun.security.x509.AlgorithmId;
import sun.security.x509.X500Name;
import sun.security.x509.KeyUsageExtension;
import sun.security.x509.PKIXExtensions;
import sun.misc.HexDumpEncoder;

/**
 * A SignerInfo, as defined in PKCS#7's signedData type.
 *
 * @author Benjamin Renaud
 * @version 1.40 02/02/00
 */
public class SignerInfo implements DerEncoder {

    BigInteger version;
    X500Name issuerName;
    BigInteger certificateSerialNumber;
    AlgorithmId digestAlgorithmId;
    AlgorithmId digestEncryptionAlgorithmId;
    byte[] encryptedDigest;

    PKCS9Attributes authenticatedAttributes;
    PKCS9Attributes unauthenticatedAttributes;

    public SignerInfo(X500Name 	issuerName,
		      BigInteger serial,
		      AlgorithmId digestAlgorithmId,
		      AlgorithmId digestEncryptionAlgorithmId,
		      byte[] encryptedDigest) {
	this.version = BigInteger.ONE;
	this.issuerName	= issuerName;
	this.certificateSerialNumber = serial;
	this.digestAlgorithmId = digestAlgorithmId;
	this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
	this.encryptedDigest = encryptedDigest;
    }

    public SignerInfo(X500Name 	issuerName,
		      BigInteger serial,
		      AlgorithmId digestAlgorithmId,
		      PKCS9Attributes authenticatedAttributes,
		      AlgorithmId digestEncryptionAlgorithmId,
		      byte[] encryptedDigest,
		      PKCS9Attributes unauthenticatedAttributes) {
	this.version = BigInteger.ONE;
	this.issuerName	= issuerName;
	this.certificateSerialNumber = serial;
	this.digestAlgorithmId = digestAlgorithmId;
	this.authenticatedAttributes = authenticatedAttributes;
	this.digestEncryptionAlgorithmId = digestEncryptionAlgorithmId;
	this.encryptedDigest = encryptedDigest;
	this.unauthenticatedAttributes = unauthenticatedAttributes;
    }

    /**
     * Parses a PKCS#7 signer info.
     */
    public SignerInfo(DerInputStream derin)
	throws IOException, ParsingException
    {
	this(derin, false);
    }

    /**
     * Parses a PKCS#7 signer info.
     *
     * <p>This constructor is used only for backwards compatibility with
     * PKCS#7 blocks that were generated using JDK1.1.x.
     *
     * @param derin the ASN.1 encoding of the signer info.
     * @param oldStyle flag indicating whether or not the given signer info
     * is encoded according to JDK1.1.x.
     */
    public SignerInfo(DerInputStream derin, boolean oldStyle)
	throws IOException, ParsingException
    {
	// version
	version = derin.getBigInteger();

	// issuerAndSerialNumber
	DerValue[] issuerAndSerialNumber = derin.getSequence(2);
	byte[] issuerBytes = issuerAndSerialNumber[0].toByteArray();
	issuerName = new X500Name(new DerValue(DerValue.tag_Sequence,
					       issuerBytes));
	certificateSerialNumber = issuerAndSerialNumber[1].getBigInteger();

	// digestAlgorithmId
	DerValue tmp = derin.getDerValue();

	digestAlgorithmId = AlgorithmId.parse(tmp);

	// authenticatedAttributes
	if (oldStyle) {
	    // In JDK1.1.x, the authenticatedAttributes are always present,
	    // encoded as an empty Set (Set of length zero)
	    derin.getSet(0);
	} else {
	    // check if set of auth attributes (implicit tag) is provided
	    // (auth attributes are OPTIONAL)
	    if ((byte)(derin.peekByte()) == (byte)0xA0) {
		authenticatedAttributes = new PKCS9Attributes(derin);
	    }
	}

	// digestEncryptionAlgorithmId - little RSA naming scheme -
	// signature == encryption...
	tmp = derin.getDerValue();

	digestEncryptionAlgorithmId = AlgorithmId.parse(tmp);

	// encryptedDigest
	encryptedDigest = derin.getOctetString();

	// unauthenticatedAttributes
	if (oldStyle) {
	    // In JDK1.1.x, the unauthenticatedAttributes are always present,
	    // encoded as an empty Set (Set of length zero)
	    derin.getSet(0);
	} else {
	    // check if set of unauth attributes (implicit tag) is provided
	    // (unauth attributes are OPTIONAL)
	    if (derin.available() != 0
		&& (byte)(derin.peekByte()) == (byte)0xA1) {
		unauthenticatedAttributes = new PKCS9Attributes(derin);
	    }
	}

	// all done
	if (derin.available() != 0) {
	    throw new ParsingException("extra data at the end");
	}
    }

    public void encode(DerOutputStream out) throws IOException {

	derEncode(out);
    }

    /**
     * DER encode this object onto an output stream.
     * Implements the <code>DerEncoder</code> interface.
     *
     * @param out
     * the output stream on which to write the DER encoding.
     *
     * @exception IOException on encoding error.
     */
    public void derEncode(OutputStream out) throws IOException {
	DerOutputStream seq = new DerOutputStream();
	seq.putInteger(version);
	DerOutputStream issuerAndSerialNumber = new DerOutputStream();
	issuerName.encode(issuerAndSerialNumber);
	issuerAndSerialNumber.putInteger(certificateSerialNumber);
	seq.write(DerValue.tag_Sequence, issuerAndSerialNumber);

	digestAlgorithmId.encode(seq);

	// encode authenticated attributes if there are any
	if (authenticatedAttributes != null)
	    authenticatedAttributes.encode((byte)0xA0, seq);

	digestEncryptionAlgorithmId.encode(seq);

	seq.putOctetString(encryptedDigest);

	// encode unauthenticated attributes if there are any
	if (unauthenticatedAttributes != null)
	    unauthenticatedAttributes.encode((byte)0xA1, seq);

	DerOutputStream tmp = new DerOutputStream();
	tmp.write(DerValue.tag_Sequence, seq);

	out.write(tmp.toByteArray());
    }



    /*
     * Returns the (user) certificate pertaining to this SignerInfo.
     */
    public X509Certificate getCertificate(PKCS7 block)
	throws IOException
    {
	return block.getCertificate(certificateSerialNumber, issuerName);
    }

    /*
     * Returns the certificate chain pertaining to this SignerInfo.
     */
    public ArrayList getCertificateChain(PKCS7 block)
	throws IOException
    {
	X509Certificate userCert;
	userCert = block.getCertificate(certificateSerialNumber, issuerName);
	if (userCert == null)
	    return null;

	ArrayList certList = new ArrayList();
	certList.add(userCert);

	X509Certificate[] pkcsCerts = block.getCertificates();
	if (pkcsCerts == null
	    || userCert.getSubjectDN().equals(userCert.getIssuerDN())) {
	    return certList;
	}

	Principal issuer = userCert.getIssuerDN();
	int start = 0;
	while (true) {
	    boolean match = false;
	    int i = start;
	    while (i < pkcsCerts.length) {
		if (issuer.equals(pkcsCerts[i].getSubjectDN())) {
		    // next cert in chain found
		    certList.add(pkcsCerts[i]);
		    // if selected cert is self-signed, we're done
		    // constructing the chain
		    if (pkcsCerts[i].getSubjectDN().equals(
                                            pkcsCerts[i].getIssuerDN())) {
			start = pkcsCerts.length;
		    } else {
			issuer = pkcsCerts[i].getIssuerDN();
			X509Certificate tmpCert = pkcsCerts[start];
			pkcsCerts[start] = pkcsCerts[i];
			pkcsCerts[i] = tmpCert;
			start++;
		    }
		    match = true;
		    break;
		} else {
		    i++;
		}
	    }
	    if (!match)
		break;
	}
	
	return certList;
    }

    /* Returns null if verify fails, this signerInfo if
       verify succeeds. */
    SignerInfo verify(PKCS7 block, byte[] data)
    throws NoSuchAlgorithmException, SignatureException {

	try {

	    ContentInfo content = block.getContentInfo();
	    if (data == null) {
		data = content.getContentBytes();
	    }

	    String digestAlgname = getDigestAlgorithmId().getName();
	    if (digestAlgname.equalsIgnoreCase("SHA"))
		digestAlgname = "SHA1";

	    byte[] dataSigned;

	    // if there are authenticate attributes, get the message
	    // digest and compare it with the digest of data
	    if (authenticatedAttributes == null) {
		dataSigned = data;
	    } else {

		// first, check content type
		ObjectIdentifier contentType = (ObjectIdentifier)
		       authenticatedAttributes.getAttributeValue(
			 PKCS9Attribute.CONTENT_TYPE_OID);
		if (contentType == null ||
		    !contentType.equals(content.contentType))
		    return null;  // contentType does not match, bad SignerInfo

		// now, check message digest
		byte[] messageDigest = (byte[])
		    authenticatedAttributes.getAttributeValue(
			 PKCS9Attribute.MESSAGE_DIGEST_OID);

		if (messageDigest == null) // fail if there is no message digest
		    return null;

		MessageDigest md = MessageDigest.getInstance(digestAlgname);
		byte[] computedMessageDigest = md.digest(data);

		if (messageDigest.length != computedMessageDigest.length)
		    return null;
		for (int i = 0; i < messageDigest.length; i++) {
		    if (messageDigest[i] != computedMessageDigest[i])
			return null;
		}

		// message digest attribute matched
		// digest of original data

		// the data actually signed is the DER encoding of
		// the authenticated attributes (tagged with
		// the "SET OF" tag, not 0xA0).
		dataSigned = authenticatedAttributes.getDerEncoding();
	    }

	    // put together digest algorithm and encryption algorithm
	    // to form signing algorithm
	    String encryptionAlgname =
		getDigestEncryptionAlgorithmId().getName();

	    if (encryptionAlgname.equalsIgnoreCase("SHA1withDSA"))
		encryptionAlgname = "DSA";
	    String algname = digestAlgname + "with" + encryptionAlgname;

	    Signature sig = Signature.getInstance(algname);
	    X509Certificate cert = getCertificate(block);

	    if (cert == null) {
		return null;
	    }
	    if (cert.hasUnsupportedCriticalExtension()) {
		throw new SignatureException("Certificate has unsupported "
					     + "critical extension(s)");
	    }

	    // Make sure that if the usage of the key in the certificate is
	    // restricted, it can be used for digital signatures.
	    // TODO: We may want to check for additional extensions in the
	    // future.
	    byte[] keyUsageBytes = cert.getExtensionValue(
	                            PKIXExtensions.KeyUsage_Id.toString());
	    if (keyUsageBytes != null) {
		KeyUsageExtension keyUsage;
		try {
		    // We don't care whether or not this extension was marked
		    // critical in the certificate.
		    // We're interested only in its value (i.e., the bits set)
		    // and treat the extension as critical.
		    keyUsage = new KeyUsageExtension(new Boolean(true),
						     keyUsageBytes);
		} catch (IOException ioe) {
		    throw new SignatureException("Failed to parse keyUsage "
						 + "extension");
		}

		boolean digSigAllowed = ((Boolean)keyUsage.get(
		        KeyUsageExtension.DIGITAL_SIGNATURE)).booleanValue();
		if (!digSigAllowed) {
		    throw new SignatureException("Key usage restricted: "
						 + "cannot be used for "
						 + "digital signatures");
		}
	    }

	    PublicKey key = cert.getPublicKey();
	    sig.initVerify(key);

	    sig.update(dataSigned);

	    if (sig.verify(encryptedDigest)) {
		return this;
	    }

	} catch (IOException e) {
	    throw new SignatureException("IO error verifying signature:\n" +
					 e.getMessage());

	} catch (InvalidKeyException e) {
	    throw new SignatureException("InvalidKey: " + e.getMessage());

	}
	return null;
    }

    /* Verify the content of the pkcs7 block. */
    SignerInfo verify(PKCS7 block)
    throws NoSuchAlgorithmException, SignatureException {
	return verify(block, null);
    }


    public BigInteger getVersion() {
	    return version;
    }

    public X500Name getIssuerName() {
	return issuerName;
    }

    public BigInteger getCertificateSerialNumber() {
	return certificateSerialNumber;
    }

    public AlgorithmId getDigestAlgorithmId() {
	return digestAlgorithmId;
    }

    public PKCS9Attributes getAuthenticatedAttributes() {
	return authenticatedAttributes;
    }

    public AlgorithmId getDigestEncryptionAlgorithmId() {
	return digestEncryptionAlgorithmId;
    }

    public byte[] getEncryptedDigest() {
	return encryptedDigest;
    }

    public PKCS9Attributes getUnauthenticatedAttributes() {
	return unauthenticatedAttributes;
    }

    public String toString() {
	HexDumpEncoder hexDump = new HexDumpEncoder();

	String out = "";

	out += "Signer Info for (issuer): " + issuerName + "\n";
	out += "\tversion: " + Debug.toHexString(version) + "\n";
	out += "\tcertificateSerialNumber: " + 
	       Debug.toHexString(certificateSerialNumber) + "\n";
	out += "\tdigestAlgorithmId: " + digestAlgorithmId + "\n";
	if (authenticatedAttributes != null) {
	    out += "\tauthenticatedAttributes: " + authenticatedAttributes +
		   "\n";
	}
	out += "\tdigestEncryptionAlgorithmId: " + digestEncryptionAlgorithmId +
	    "\n";

	out += "\tencryptedDigest: " + "\n" +
	    hexDump.encodeBuffer(encryptedDigest) + "\n";
	if (unauthenticatedAttributes != null) {
	    out += "\tunauthenticatedAttributes: " +
		   unauthenticatedAttributes + "\n";
	}
	return out;
    }

}




